#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>

#include "../common/logger.h"

/*
Implement a function, one_time_init(control, init), that performs the equivalent of
pthread_once(). The control argument should be a pointer to a statically allocated
structure containing a Boolean variable and a mutex. The Boolean variable
indicates whether the function init has already been called, and the mutex controls
access to that variable. To keep the implementation simple, you can ignore
possibilities such as init() failing or being canceled when first called from a thread
(i.e., it is not necessary to devise a scheme whereby, if such an event occurs, the
next thread that calls one_time_init() reattempts the call to init())
 */

struct once_control {
        bool is_inited;
        pthread_mutex_t mutex;
};

void init(struct once_control *control)
{
        control->is_inited = false;
        pthread_mutex_init(&control->mutex, NULL);
}

void one_time_init(struct once_control *control,
                   void (*init_routine)(void))
{
        pthread_mutex_lock(&control->mutex);
        if (!control->is_inited) {
                control->is_inited = true;
                init_routine();
        }
        pthread_mutex_unlock(&control->mutex);
}

static int gettid(void)
{
        return syscall(SYS_gettid);
}

static void init_func(void)
{
        printf("only one init\n");
}

static void *thread_func(void *arg)
{
        printf("in thread %d\n", gettid());
        struct once_control *control = arg;
        one_time_init(control, init_func);
}

int main(void)
{
        struct once_control control;
        init(&control);
        pthread_t thread_a, thread_b;
        check_system_error(
                pthread_create(&thread_a, NULL, thread_func, &control),
                "pthread_create");
        check_system_error(
                pthread_create(&thread_b, NULL, thread_func, &control),
                "pthread_create");
        check_system_error(pthread_join(thread_a, NULL), "pthread_join");
        check_system_error(pthread_join(thread_b, NULL), "pthread_join");

        return EXIT_SUCCESS;
}
